package com.googlecode.cswish.util.bean;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.persistence.ManyToOne;
import javax.persistence.Transient;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.collection.internal.PersistentBag;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.cglib.proxy.Enhancer;

import com.googlecode.cswish.annotation.QLFormula;
import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.PropertyInfo;
import com.googlecode.cswish.struts.hibernate.HibernateUtil;
import com.googlecode.cswish.struts.hibernate.IDynamicModel;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.Executor;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.util.ClassUtil;
import com.googlecode.cswish.util.OgnlExUtil;
import com.googlecode.cswish.util.bean.ModelScanner.ScannerCallback;
import com.googlecode.cswish.util.bean.ModelScanner.ScannerFlag;

/**
 * <b>Description:</b><br>
 * Utils for entity model, create proxied model, register force load propties, etc.
 * 
 * <p>
 */
public class ModelUtil {

	/**
	 * Logger for this class
	 */
	private static final Log LOGGER = LogFactory.getLog(ModelUtil.class);
	
	private static final Object NULL = new Object();
	
	private final static GenericService genericService = (GenericService) ObjectFactory.getInstance().getBean(GenericService.class, false);
	
	private static OgnlExUtil ognlExUtil;
	
	private static Map<Class, List<Field>> classFieldCache = new WeakHashMap<>();
	
	private static OgnlExUtil getOgnlExUtil() {
		if (ognlExUtil == null) {
			ognlExUtil = (OgnlExUtil) ObjectFactory.getInstance().getBean(OgnlExUtil.class, true);
		}
		return ognlExUtil;
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T newInstance(Class<T> clazz) {
		return new0(clazz, null);
	}
	
	/**
	 * Build instance with initialize parameters
	 * 
	 * @param clazz
	 * @param parameters
	 * @return
	 */
	public static <T> T new0(Class<T> clazz, Object ... parameters) {
		if (Enhancer.isEnhanced(clazz) || HibernateProxy.class.isAssignableFrom(clazz)) {
			clazz = (Class<T>) clazz.getSuperclass();
		}
		
		T instance;
		try {
			if (clazz.isInterface()) {
				if (Collection.class.isAssignableFrom(clazz)) {
					instance = (T) new ArrayList();
				} else if (Map.class.isAssignableFrom(clazz)) {
					instance = (T) new HashMap();
				} else {
					instance = (T) clazz.newInstance();
				}
			} else {
				instance = (T) clazz.newInstance();
			}
		} catch (IllegalAccessException e) {
			throw new RuntimeException("Default constructor is needed!!!");
		} catch (InstantiationException e) {
			throw new RuntimeException("Default constructor is needed!!!");
		}
		
		if (parameters != null && parameters.length > 0) {
			if (parameters.length % 2 == 1) {
				throw new RuntimeException("Wrong parameter count");
			}
			
			for (int i = 0; i < parameters.length; i++, i++) {
				String name = (String) parameters[i];
				Object value = parameters[i + 1];
				setProperty(instance, name, value);
			}
		}
		return instance;
	}
	
	/**
	 * Copy each sub property from 'from' to 'to', don't copy the null value except the property defined in
	 * 'nullValueProperties'
	 * 
	 * @param from source model
	 * @param to dest model
	 * @param enableProperties only update the enabled not-null properties, <b>null</b> means update all the not-null property
	 * @param nullValueProperties special fields, update the null vale if nullValueProperties is defined.
	 */
	public static void deepUpdate(Object from, Object to, Executor<ScannerContext, ScannerContext> entityInterceptor, 
			Collection<String> enableProperties, Collection<String> nullValueProperties) {
		Map<Object, Object> identityMap = new IdentityHashMap<Object, Object>();
		if (nullValueProperties == null) {
			nullValueProperties = Collections.EMPTY_LIST;
		}
		ScannerCallback scannerCallback = new DeepUpdateScannerCallback(entityInterceptor, enableProperties, nullValueProperties, identityMap);
		deepCopy(from, to, null, scannerCallback, identityMap);
	}

	protected static void deepCopy(Object from, Object to, ScannerContext parentContext,
			ScannerCallback scannerCallback, Map<Object, Object> identityMap) {
		ModelScanner.getInstance().scan(from, to, parentContext, scannerCallback, identityMap);
	}
	
	public static <T> T deepCopy(T from, T to, final boolean skipTransientField, final List<String> disabledProperties) {
		final Pattern pattern;
		if (disabledProperties != null) {
			String expression = StringUtils.join(disabledProperties, "|");
			pattern = Pattern.compile(expression);
		} else {
			pattern = null;
		}
		
		final Map<Object, Object> identityMap = new IdentityHashMap<Object, Object>();
		identityMap.put(from, to);
		ModelScanner.getInstance().scan(from, to, new ScannerCallback() {
			@Override
			public ScannerFlag proceed(ScannerContext scannerContext) {
				if (skipTransientField && !scannerContext.getPropertyDescriptor().isTableColumn()) {
					return ScannerFlag.SCANSIBLING;
				}
				
				if (scannerContext.isSimpleObj()) {
					Object value = scannerContext.getSrcPropertyValue();
					scannerContext.setDestPropertyValue(value);
					return ScannerFlag.SCANSIBLING;
				} else if (scannerContext.isCollection()){
					List<Object> copyList = new ArrayList<Object>();
					
					Collection<Object> fromColl = (Collection<Object>)scannerContext.getSrcPropertyValue();
					if (fromColl != null && fromColl.size() > 0) {
						for (Object fromItem : fromColl) {
							Object toItem = tryToNewInstance(fromItem, scannerContext.getParentPath(),
									scannerContext.getPropertyDescriptor().getName());
							if (toItem != null) {
								copyList.add(toItem);
							}
						}
						if (copyList.size() > 0) {
							scannerContext.setDestPropertyValue(copyList);
							return ScannerFlag.SCANCHILD;
						} else {
							return ScannerFlag.SCANSIBLING;
						}
					} else {
						return ScannerFlag.SCANSIBLING;
					}
				} else {
					Object fromItem = scannerContext.getSrcPropertyValue();
					String property = scannerContext.getPropertyDescriptor().getName();
					Object value = tryToNewInstance(fromItem, scannerContext.getParentPath(), property);
					scannerContext.setDestPropertyValue(value);
					
					if (value != null && fromItem != value) {
						return ScannerFlag.SCANCHILD;
					}
					return ScannerFlag.SCANSIBLING;
				}
			}
			
			private Object tryToNewInstance(Object fromItem, String parentPath, String property) {
				if (fromItem == null) {
					return null;
				}
				
				if (pattern != null) {
					String path = parentPath == null ? property : parentPath + '.' + property;
					Matcher matcher = pattern.matcher(path);
					if (matcher.matches()) {
						// check end property
						return null;		// don't enable to copy
					}
				}
				
				Object existed = identityMap.get(fromItem);
				Object toItem;
				if (existed != null) {
					toItem = existed;		// the object has been copied, use the existed copy
				} else {
					if (fromItem instanceof HibernateProxy) {
						Object element = HibernateUtil.unproxy(fromItem);
						toItem = newInstance(element.getClass());
					} else {
						toItem = newInstance(fromItem.getClass());		// create a new copy
					}
					identityMap.put(fromItem, toItem);
				}
				return toItem;
			}
		});
		return to;
	}
	
	/**
	 * Update the ManyToOne property, only update the db model
	 * 
	 *  check the child property type, set the parent model value when it's null
	 * 
	 * TODO: remove parameter 'loadDbInstanceIfIdExists' and split the method inot 2 method?
	 * 
	 * @param model
	 */
	public static void updateParentChildRelationshipAndDbModel(Object model, 
			final Executor<ScannerContext, ScannerContext> entityInterceptor) {
		ModelScanner.getInstance().scan(model, model, new ScannerCallback() {
			@Override
			public ScannerFlag proceed(ScannerContext scannerContext) {
				// 1. ignore the simple property & transient property
				PropertyInfo propertyInfo = scannerContext.getPropertyDescriptor();
				if (propertyInfo.isSimpleObj()) {
					return ScannerFlag.SCANSIBLING;
				}
				if (!propertyInfo.isTableColumn()) {
					return ScannerFlag.SCANSIBLING;
				}
				
				// 2. check ManyToOne annotation and get the targe entity class if current property value is null
				Class targetEntity = null;
				if (propertyInfo.isDynamic()) {
					Method readMethod = propertyInfo.getReadMethod();
					if (!List.class.getName().equals(propertyInfo.getType())) {
						if (scannerContext.getSrcPropertyValue() == null) {
							targetEntity = readMethod.getReturnType();
						}
					}
				} else {
					Method readMethod = propertyInfo.getReadMethod();
					ManyToOne manyToOne = readMethod.getAnnotation(ManyToOne.class);
					if (manyToOne != null && entityInterceptor != null) {
						entityInterceptor.execute(scannerContext);
					}
					if (manyToOne != null && scannerContext.getSrcPropertyValue() == null) {
						targetEntity = manyToOne.targetEntity();
						if (targetEntity == null || void.class.equals(targetEntity)) {
							targetEntity = readMethod.getReturnType();
						}
					}
				}
				
				// 3. update parent/child relationship (for example: ModelField.model): 
				// try to set the parent model value to current property value
				// TODO: don't update the same child with curent model? see updateParentChildRelationship
				if (targetEntity != null) {
					ScannerContext scannerParent = scannerContext.getParentContext();
					if (scannerParent != null) {
						if (targetEntity.isAssignableFrom(scannerParent.getSrcModel().getClass())) {
							scannerContext.setSrcPropertyValue(scannerParent.getSrcModel());
						}
					}
					
					// check if the bean contains id, use the db model if bean contains id
					Integer id = (Integer) getSimpleProperty(scannerContext.getSrcPropertyValue(), "id");
					if (id != null) {
						Object dbItem = genericService.load(targetEntity, id);
						if (dbItem != null) {
							scannerContext.setSrcPropertyValue(dbItem);
						}
					}
					return ScannerFlag.SCANSIBLING;
				} else {
					Object subBean = scannerContext.getSrcPropertyValue();
					if (modelFromDb(subBean, scannerContext)) {
						return ScannerFlag.SCANSIBLING;
					} else {
						boolean listFromDb = subBean instanceof PersistentCollection;
						// check if the sub bean contains id, use the db model if sub bean contains id 
						if (!listFromDb && subBean instanceof Collection) {
							Collection coll = (Collection)subBean;
							List<Object> newColl = null;
							for (Object subItem : coll) {
								Integer id = (Integer) getSimpleProperty(subItem, "id");
								if (id != null) {
									Object dbItem = genericService.load(ClassUtil.getClass(subItem), id);
									if (dbItem != null && dbItem != subItem) {
										if (newColl == null) {
											newColl = new ArrayList<Object>(coll.size()); 
										}
										newColl.add(dbItem);
									}
								}
							}
							if (newColl != null) {
								scannerContext.setSrcPropertyValue(newColl);
								listFromDb = true;
							}
						}
						return listFromDb ? ScannerFlag.SCANSIBLING : ScannerFlag.SCANCHILD;
					}
				}
			}
			
			private boolean modelFromDb(Object model, ScannerContext scannerContext) {
				if (model == null || model instanceof Collection) {
					return false;
				}
				
				Object id = getSimpleProperty(model, "id");
				boolean fromDB = model instanceof HibernateProxy;
				if (!fromDB && id != null) {
					Object dbValue = genericService.load(ClassUtil.getClass(model), id);
					scannerContext.setSrcPropertyValue(dbValue);
					fromDB = true;
					// TODO: copy the not-null property from model to dbValue?
					if (model != dbValue) {
						
					}
				} 
				return fromDB;
			}
		});
	}
	
	public static void updateParentChildRelationship(Object model, final Executor<ScannerContext, ScannerContext> entityInterceptor) {
		ModelScanner.getInstance().scan(model, model, new ScannerCallback() {
			@Override
			public ScannerFlag proceed(ScannerContext scannerContext) {
				// 1. ignore the simple property & transient property
				PropertyInfo propertyInfo = scannerContext.getPropertyDescriptor();
				if (propertyInfo.isSimpleObj()) {
					return ScannerFlag.SCANSIBLING;
				}
				if (!propertyInfo.isTableColumn()) {
					return ScannerFlag.SCANSIBLING;
				}
				
				// 2. check ManyToOne annotation and get the targe entity class when current property value is null
				Class targetEntity = null;
				if (propertyInfo.isDynamic()) {
					Method readMethod = propertyInfo.getReadMethod();
					if (!List.class.getName().equals(propertyInfo.getType())) {
						if (scannerContext.getSrcPropertyValue() == null) {
							targetEntity = readMethod.getReturnType();
						}
					}
				} else {
					Method readMethod = propertyInfo.getReadMethod();
					ManyToOne manyToOne = readMethod.getAnnotation(ManyToOne.class);
					if (manyToOne != null && entityInterceptor != null) {
						entityInterceptor.execute(scannerContext);
					}
					if (manyToOne != null && scannerContext.getSrcPropertyValue() == null) {
						targetEntity = manyToOne.targetEntity();
						if (targetEntity == null || void.class.equals(targetEntity)) {
							targetEntity = readMethod.getReturnType();
						}
					}
				}
				
				// 3. update parent/child relationship (for example: ModelField.model): 
				// try to set the parent model value to current property value
				// 3.1: don't update the child type which has the same type with current model
				// (such as Menu.parent is Menu, don't update it)
				if (targetEntity != null && !targetEntity.equals(ClassUtil.getClass(scannerContext.getSrcModel()))) {
					ScannerContext scannerParent = scannerContext.getParentContext();
					if (scannerParent != null) {
						// 3.2 the parent model value is the matched type
						if (targetEntity.isAssignableFrom(scannerParent.getSrcModel().getClass())) {
							scannerContext.setSrcPropertyValue(scannerParent.getSrcModel());
						}
					}
					return ScannerFlag.SCANSIBLING;
				} else {
					if (scannerContext.getDestPropertyValue() instanceof PersistentCollection) {
						return ScannerFlag.SCANSIBLING;
					} else {
						return ScannerFlag.SCANCHILD;
					}
				}
			}
		});
	}
	
	/**
	 * Copy and convert the property
	 * 
	 * @param from
	 * @param to
	 */
	public static void copyProperties(Map<String, Object> values, Object destModel) {
		for (Entry<String, Object> entry : values.entrySet()) {
			setProperty(destModel, entry.getKey(), entry.getValue());
		}
	}
	
	/**
	 * Only copy the first level children
	 * 
	 * @param from
	 * @param to
	 */
	public static void copy(Object from, Object to, boolean enableConversion) {
		if (enableConversion) {
			Map<String, Object> properties = describe(from);
			copyProperties(properties, to);
		} else {
			copy(from, to);
		}
	}
	
	/**
	 * Only copy the first level children
	 * 
	 * @param from
	 * @param to
	 */
	public static void copy(Object from, Object to) {
		if (from == null || to == null) {
			return ;
		}
		
		ModelScanner.getInstance().scan(from, to, new ScannerCallback() {
			@Override
			public ScannerFlag proceed(ScannerContext scannerContext) {
				Object value = scannerContext.getSrcPropertyValue();
				scannerContext.setDestPropertyValue(value);
				return ScannerFlag.SCANSIBLING;
			}
		});
	}
	
	public static void deepCopyOnFields(Object from, Object to) {
		Map<Object, Object> identityMap = new IdentityHashMap<Object, Object>();
		deepCopyOnFields(from, to, true, identityMap);
	}
	/**
	 * Be similar with copy method, but the it bases on the field level
	 * 
	 * @param from
	 * @param to
	 * @param shareListMapProperty
	 */
	public static void copyOnFields(Object from, Object to) {
		deepCopyOnFields(from, to, false, null);
	}
	
	private static void deepCopyOnFields(Object from, Object to, boolean deep, Map<Object, Object> identityMap) {
		if (identityMap != null) {
			identityMap.put(from, to);
		}
		try {
			Class clazz = from.getClass();
			List<Field> fields = getAllDeclaredFields(clazz);
			Map<String, Field> fieldMapping = null;
			if (!clazz.equals(to.getClass())) {
				List<Field> toFields = getAllDeclaredFields(to.getClass());
				fieldMapping = new HashMap<String, Field>();
				for (Field toField : toFields) {
					fieldMapping.put(toField.getName(), toField);
				}
			}
			for (Field field : fields) {
				field.setAccessible(true);
				Object val = HibernateUtil.unproxy(field.get(from));
				
				if (!Modifier.isFinal(field.getModifiers())) {
					Field toField;
					if (fieldMapping != null) {
						toField = fieldMapping.get(field.getName());
					} else {
						toField = field;
					}
					if (toField != null) {
						// clone the complex object
						if (val != null && deep && !ClassUtil.isSimpleObj(toField.getType())) {
							Object existed = identityMap.get(val);
							if (existed != null) {
								val = existed;
							} else {
								Object cloneVal = newInstance(val.getClass());
								if (val instanceof Collection) {
									Collection coll = (Collection)val;
									Collection cloneColl = (Collection)cloneVal;
									for (Object item : coll) {
										Object clonedItem = newInstance(item.getClass());
										deepCopyOnFields(item, clonedItem, deep, identityMap);
										cloneColl.add(clonedItem);
									}
								} else if (val instanceof Map) {
									Map map = (Map)val;
									Map cloneMap = (Map)cloneVal;
									Set<Map.Entry> set = map.entrySet();
									for (Entry item : set) {
										if (item.getValue() != null) {
											Object clonedItem = newInstance(item.getValue().getClass());
											deepCopyOnFields(item, clonedItem, deep, identityMap);
											cloneMap.put(item.getKey(), clonedItem);
										} else {
											cloneMap.put(item.getKey(), null);
										}
									}
								} else {
									deepCopyOnFields(val, cloneVal, deep, identityMap);
								}
								val = cloneVal;
							} 
						}
						toField.set(to, val);
					}
				}
			}
		} catch (Exception ex) {
			LOGGER.error("Failed to clone the persistence instance", ex);
		}
	}
	
	public static List<Field> getAllDeclaredFields(Class clazz) {
		List<Field> fields = classFieldCache.get(clazz);
		
		if (fields == null) {
			fields = new ArrayList<Field>();
			populateAllDeclaredFields(clazz, fields);
			classFieldCache.put(clazz, fields);
		}
		return fields;
	}
	
	private static void populateAllDeclaredFields(Class clazz, List<Field> fieldList) {
		
		
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			fieldList.add(field);
		}
		Class superClazz = clazz.getSuperclass();
		if (superClazz != null) {
			populateAllDeclaredFields(superClazz, fieldList);
		}
	}
	
	/**
	 * Only copy the first level non-null value
	 * 
	 * @param from
	 * @param to
	 */
	public static void copyIgnoreNullValue(Object from, Object to) {
		ModelScanner.getInstance().scan(from, to, new ScannerCallback() {
			@Override
			public ScannerFlag proceed(ScannerContext scannerContext) {
				Object value = scannerContext.getSrcPropertyValue();
				if (value != null) {
					scannerContext.setDestPropertyValue(value);
				}
				return ScannerFlag.SCANSIBLING;
			}
		});
	}
	
	public static void deepCopyIgnoreNullValue(Object from, Object to) {
		ModelScanner.getInstance().scan(from, to, new ScannerCallback() {
			@Override
			public ScannerFlag proceed(ScannerContext scannerContext) {
				Object value = scannerContext.getSrcPropertyValue();
				ScannerFlag flag = ScannerFlag.SCANSIBLING;
				if (value != null) {
					if (scannerContext.isSimpleObj() || scannerContext.getDestPropertyValue() == null) {
						scannerContext.setDestPropertyValue(value);
					} else {
						flag = ScannerFlag.SCANCHILD;
					}
				}
				return flag;
			}
		});
	}
	
	public static void deepCopy(Object from, Object to, Collection<String> enabledFields) {
		deepCopy(from, to, enabledFields, true);
	}
	
	private static Set<String> toFields(Collection<String> filterFields, boolean isEnabledField) {
		if (filterFields == null) {
			return Collections.EMPTY_SET;
		}
		
		Set<String> fields = new HashSet<String>();
		if (isEnabledField) {
			for (String field : filterFields) {
				int index = 0;
				while (true) {
					int nextIndex = field.indexOf('.', index);
					if (nextIndex != -1) {					
						fields.add(field.substring(0, nextIndex));
						index = nextIndex + 1;
					} else {
						if (index == 0) {
							fields.add(field);
						}
						break;
					}
				}
			}
		} else {
			fields.addAll(filterFields);
		}
		return fields;
	}
	
	/**
	 * deep copy the property from one object to another object
	 * 
	 * @param from
	 * @param to
	 * @param filterFields
	 * 
	 * @param isEnabledField identify that the 'filterField' is disabled field or enabled field 
	 */
	public static void deepCopy(Object from, Object to, Collection<String> filterFields, final boolean isEnabledField) {
		// add the full path fields
		final Set<String> fields = toFields(filterFields, isEnabledField);
		
		// copy
		ModelScanner.getInstance().scan(from, to, new ScannerCallback() {
			@SuppressWarnings({ "rawtypes", "unchecked" })
			@Override
			public ScannerFlag proceed(ScannerContext scannerContext) {
				String path = scannerContext.getChildPath();
				if (isEnabledField && fields.contains(path) || !isEnabledField && !fields.contains(path)) {
					Object value = scannerContext.getSrcPropertyValue();
					if (scannerContext.isSimpleObj()) {
						scannerContext.setDestPropertyValue(value);
						return ScannerFlag.SCANSIBLING;
					} else {
						if (value != null) {
							Object destPropertyValue = new0(scannerContext.getPropertyDescriptor().getTypeClass());
							if (destPropertyValue instanceof Collection) {
								Collection fromColl = (Collection)value;
								Collection toColl = (Collection) destPropertyValue;
								for (Object fromItem : fromColl) {
									Object toItem = new0(fromItem.getClass());
									toColl.add(toItem);
								}
							} else {
								Object unProxyValue = HibernateUtil.unproxy(value);
								if (unProxyValue != value) {
									scannerContext.setSrcPropertyValueWithoutModify(unProxyValue);
								}
							}
							scannerContext.setDestPropertyValue(destPropertyValue);
						} else {
							scannerContext.setDestPropertyValue(null);
						}
						return ScannerFlag.SCANCHILD;
					}
				}
				return ScannerFlag.SCANSIBLING;
			}
		});
	}
	
	public static Map<String, Object> deepDescribe(Object model) {
		final Map<String, Object> map = new HashMap<String, Object>();
		ModelScanner.getInstance().scan(model, null, new ScannerCallback() {
			@Override
			public ScannerFlag proceed(ScannerContext scannerContext) {
				if (scannerContext.isSimpleObj()) {
					String name = scannerContext.getChildPath();
					Object value = scannerContext.getSrcPropertyValue();
					if (value != null) {
						map.put(name, value);
					}
					return ScannerFlag.SCANSIBLING;
				} else {
					return ScannerFlag.SCANCHILD;
				}
			}
		});
		return map;
	}
	
	public static Map<String, Object> describe(Object model) {
		final Map<String, Object> map = new HashMap<String, Object>();
		ModelScanner.getInstance().scan(model, null, new ScannerCallback() {
			@Override
			public ScannerFlag proceed(ScannerContext scannerContext) {
				String name = scannerContext.getChildPath();
				Object value = scannerContext.getSrcPropertyValue();
				if (value != null) {
					map.put(name, value);
				}
				return ScannerFlag.SCANSIBLING;
			}
		});
		return map;
	}
	
	public static Map<String, Object> deepDescribeSearchModel(Object model) {
		if (model instanceof Map) {
			return (Map<String, Object>) model;
		}
		
		final Map<String, Object> map = new HashMap<String, Object>();
		ModelScanner.getInstance().scan(model, null, new ScannerCallback() {
			@Override
			public ScannerFlag proceed(ScannerContext scannerContext) {
				if (scannerContext.isSimpleObj()) {
					String name = scannerContext.getChildPath();
					Object value = scannerContext.getSrcPropertyValue();
					if (value != null) {
						map.put(name, value);
					}
					return ScannerFlag.SCANSIBLING;
				} else {
					Method readMethod = scannerContext.getPropertyDescriptor().getReadMethod();
					if (readMethod != null) {
						// don't populate transient field except it defined QLFormula
						if (readMethod.getAnnotation(Transient.class) == null
								|| readMethod.getAnnotation(QLFormula.class) != null) {
							return ScannerFlag.SCANCHILD;
						}
					}
					return ScannerFlag.SCANSIBLING;
				}
			}
		});
		return map;
	}
	
	/**
	 * TODO: Move it to ClassUtil
	 * @return
	 */
	public static Class getGenericReturnType(Method method) {
		String genericInfo = method.getGenericReturnType().toString();
		int begin = genericInfo.indexOf('<');
		int end = genericInfo.indexOf('>', begin);
		String className = genericInfo.substring(begin + 1, end);
		try {
			return Thread.currentThread().getContextClassLoader().loadClass(className);
		} catch (ClassNotFoundException e) {
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("Can't find class: " + className);
			}
			return null;
		}
	}
	
	public static Object getSimpleProperty(Object bean, String name) {
		return getSimpleProperty(bean, name, false);
	}

	private static Object getSimpleProperty(Object bean, String name, boolean autoCreate) {
		if (bean == null) {
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("Can not get " + name + " from null bean");
			}
			return null;
		}
		
		Integer subscriptIndex = null;
		String filterProperty = null;
		String filter = null;
		if (name.charAt(name.length() - 1) == ']') {
			int subscriptBegin = name.lastIndexOf('[');
			if (subscriptBegin < name.length() - 2) {
				String strIndex = name.substring(subscriptBegin + 1, name.length() - 1);
				if (NumberUtils.isNumber(strIndex)){
					subscriptIndex = Integer.valueOf(strIndex);
					name = name.substring(0, subscriptBegin);
				} else {
					String filterValue = name.substring(subscriptBegin + 1, name.length() - 1);
					int pos = filterValue.indexOf("=");
					if (pos > 0){
						filterProperty = filterValue.substring(0, pos);
						filter = filterValue.substring(pos + 1);
					}
					name = name.substring(0, subscriptBegin);
				}
			}
		} else if (name.charAt(name.length() - 1) == ')'){
			int subscriptBegin = name.lastIndexOf('(');
			if (subscriptBegin < name.length() - 2) {
				filter = name.substring(subscriptBegin + 1, name.length() - 1);
				filterProperty = "key";
			}
			name = name.substring(0, subscriptBegin);
		}
		
		
		Object value;
		PropertyInfo propertyInfo = null;
		if (bean instanceof Map) {
			value = ((Map) bean).get(name);
		} else {
			String className = ClassUtil.getClass(bean).getName();
			ModelInfo modelInfo =  BeanIntrospector.getInstance().getModelInfo(
					className);
			propertyInfo = modelInfo.getProperty(name);

			if (propertyInfo == null) {
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Can not get PropertyInfo of " + className + '#' + name);
				}
				value = null;
			} else {
				value = propertyInfo.getValue(bean, modelInfo);
				if (autoCreate && value == null) {
					value = newInstance(propertyInfo.getReadMethod().getReturnType());
					propertyInfo.setValue(bean, value, modelInfo);
				}
			}
		}
		//TODO: better solution ?.
		if (filterProperty != null && filter != null && value != null) {
			List results = new ArrayList();
			if (value instanceof Collection) {
				Iterator iter = ((Collection)value).iterator();
				while (iter.hasNext()) {
					Object item = iter.next();
					Object val =  getSimpleProperty(item, filterProperty);
					if (val != null){
						String sVal = val.toString();
						if (sVal.equals(filter)){
							results.add(item);
						}
					}
				}
				value = results;
			} else if (value instanceof Map) {
				value = ((Map) value).get(filter);
			} else {
				value = null;
			}
		}
		//end
		
		if (subscriptIndex != null && value != null) {
			Object item;
			// get the value from collection or array
			if (value instanceof List) {
				List list = (List) value;
				if (subscriptIndex < list.size()) {
					item = list.get(subscriptIndex);
				} else {
					item = null;
				}
			} else if (value instanceof Collection) {
				Iterator iter = ((Collection)value).iterator();
				int i = subscriptIndex;
				item = null;
				while (i >= 0 && iter.hasNext()) {
					item = iter.next();
					i--;
				}
				if (i != 0) {
					item = null;
				}
			} else if (value.getClass().isArray()) {
				// TODO: check length
				item = Array.get(value, subscriptIndex);
			} else {
				item = null;
			}
			
			if (autoCreate && item == null && propertyInfo != null) {
				item = newInstance(propertyInfo.getGenericTypeClass());
				if (value instanceof List) {
					List list = (List) value;
					for (int i = list.size(), maxIndex = subscriptIndex; i <= maxIndex; i++ ) {
						list.add(null);
					}
					list.set(subscriptIndex, item);
				} else if (value instanceof Collection) {
					// TODO: how to set the value?
					Collection coll = (Collection)value;
					for (int i = coll.size(), maxIndex = subscriptIndex; i <= maxIndex; i++ ) {
						coll.add(null);
					}
					
				} else if (value.getClass().isArray()) {
					// TODO: increase the size
					Array.set(value, subscriptIndex, item);
				}
			}
			value = item;
		}
		
		return value;
	}

	public static void setSimpleProperty(Object bean, String name, Object value) {
		if (bean == null) {
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("Can not set " + name + " to null bean");
			}
			return;
		}
		if (bean instanceof Map) {
			((Map) bean).put(name, value);
			return ;
		}

		ModelInfo modelInfo = BeanIntrospector.getInstance().getModelInfo(bean.getClass().getName());
		PropertyInfo propertyInfo = modelInfo.getProperty(name);
		if (propertyInfo == null) {
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("Can not get PropertyInfo of " + bean.getClass().getName() + '#' + name);
			}
		} else {
			// value type is different with the dest type
			if (value != null) {
				Method readMethod = propertyInfo.getReadMethod();
				if (readMethod != null) {
					if (!readMethod.getReturnType().isAssignableFrom(value.getClass())) {
						// convert the type
						value = getOgnlExUtil().convertValue(value, propertyInfo.getReadMethod().getReturnType(), name);
					}
				} else if (!propertyInfo.getTypeClass().isAssignableFrom(value.getClass())) {
					// convert the type
					value = getOgnlExUtil().convertValue(value, propertyInfo.getTypeClass(), name);
				}
			}
			propertyInfo.setValue(bean, value, modelInfo);
		}
	}

	/**
	 * Get the bean property.
	 * <p>
	 * Faster than PropertyUtilsBean.getProperty, don't support [] and DynaBean
	 * 
	 * @param bean
	 * @param name
	 * @return
	 */
	public static Object getProperty(Object bean, String name) {
		return getProperty(bean, name, false);
	}

	public static Object getProperty(Object bean, String name, boolean autoCreate) {
		Object value;
		if (name == null) return null;
		// PropertyUtils doesn't support '[*]', parse it by the following logic
		int index = name.indexOf('.');

		String subProperty = null;
		if (index > 0) {
			value = getSimpleProperty(bean, name.substring(0, index), autoCreate);
			subProperty = name.substring(index + 1); // skip '.'	
		} else {
			value = bean;
			subProperty = name;
		}
		
		if (value instanceof Collection) {
			Collection list = (Collection) value;
			List<Object> ret = new ArrayList<Object>(list.size());
			for (Object item : list) {
				Object subValue = getProperty(item, subProperty, autoCreate);
				boolean isListResult = subValue instanceof Collection;
				if (isListResult) {
					ret.addAll((Collection)subValue);
				} else {
					ret.add(subValue);
				}
			}
			value = ret;
		} else if (index > 0) {
			value = getProperty(value, subProperty, autoCreate);
		} else {
			value = getSimpleProperty(value, subProperty, autoCreate);
		}
		return value;
	}

	/**
	 * set bean's property
	 * 
	 * @param bean
	 * @param name
	 * @param value
	 */
	public static void setProperty(Object bean, String name, Object value) {
		int index = name.lastIndexOf('.');
		if (index > 0) {
			Object subBean = getProperty(bean, name.substring(0, index), true);
			if (subBean != null) {
				setSimpleProperty(subBean, name.substring(index + 1), value);
			} else {
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Failed to set property, because the value of " + bean.getClass().getName()
							+ "." + name.substring(0, index) + " is null");
				}
			}
		} else {
			setSimpleProperty(bean, name, value);
		}
	}
	
	public static void setField(Object bean, String name, Object value) {
		Class clazz = null;
		try {
			clazz = ClassUtil.getClass(bean);
			Field field = clazz.getDeclaredField("results");
			field.setAccessible(true);
			field.set(bean, value);
		} catch (Exception e) {
			LOGGER.error("Faild to set field:" + clazz.getName() + '#' + name);
		}
	}

	public static interface IdStrategy {
		void execute(ScannerContext scannerContext);
	}
	
	public static <T> T detatch(T from, IdStrategy idStrateg) {
		return detatch(from, null, idStrateg);
	}
	
	public static <T> T detatch(T from, Collection<String> disableFields, IdStrategy idStrategy) {
		final Map<Object, Object> identityMap = new IdentityHashMap<Object, Object>();
		T to = (T) newInstance(ClassUtil.getClass(from));
		identityMap.put(from, to);
		
		final Collection<String> fields = disableFields;
		
		class Callback implements ScannerCallback {
			private IdStrategy idStrategy;
			
			public Callback(IdStrategy idStrategy) {
				this.idStrategy = idStrategy;
			}
			
			@Override
			public ScannerFlag proceed(ScannerContext scannerContext) {
				if (fields != null && !scannerContext.isSimpleObj()) {
					String path = scannerContext.getChildPath();
					for (String filterField : fields) {
						int index;
						if (path.endsWith(filterField) || (index = path.indexOf(filterField)) >= 0 
								&& path.charAt(index + filterField.length()) == '.') {
							return ScannerFlag.SCANSIBLING;
						}
					}
				}
				
				boolean isTableColumn = scannerContext.getPropertyDescriptor().isTableColumn();
				if (!isTableColumn) {
					return ScannerFlag.SCANSIBLING;
				}
				
				if (scannerContext.isSimpleObj()) {
					if (scannerContext.getSrcModel() != scannerContext.getDestModel()) {
						if (idStrategy != null && scannerContext.getPropertyDescriptor().isId()) {
							idStrategy.execute(scannerContext);
						} else {
							scannerContext.setDestPropertyValue(scannerContext.getSrcPropertyValue());
						}
					}
					return ScannerFlag.SCANSIBLING;
				} else if (scannerContext.getSrcPropertyValue() instanceof HibernateProxy){
					HibernateProxy hibernateProxy = (HibernateProxy)scannerContext.getSrcPropertyValue();
					if (hibernateProxy.getHibernateLazyInitializer().isUninitialized()) {
						scannerContext.setDestPropertyValue(null);
						identityMap.put(hibernateProxy, null);
						return ScannerFlag.SCANSIBLING;
					} else {
						Object newInstance = tryToNewInstance(hibernateProxy);
						scannerContext.setDestPropertyValue(newInstance);
						identityMap.put(hibernateProxy, newInstance);
						return ScannerFlag.SCANCHILD;
					}
				} else {
					if (scannerContext.getSrcPropertyValue() instanceof PersistentBag) {
						PersistentBag persistentBag = (PersistentBag) scannerContext.getSrcPropertyValue();
						if (persistentBag.wasInitialized()) {
							List result = new ArrayList(persistentBag.size());
							for (Object item : persistentBag) {
								Object newItem = tryToNewInstance(item);
								result.add(newItem);
							}
							scannerContext.setDestPropertyValue(result);
							identityMap.put(scannerContext.getSrcPropertyValue(), result);
							return ScannerFlag.SCANCHILD;
						} else {
							identityMap.put(scannerContext.getSrcPropertyValue(), null);
							return ScannerFlag.SCANSIBLING;
						}
					} else {
						Object newItem = tryToNewInstance(scannerContext.getSrcPropertyValue());
						scannerContext.setDestPropertyValue(newItem);
						identityMap.put(scannerContext.getSrcPropertyValue(), newItem);
						return ScannerFlag.SCANCHILD;
					}
				}
			}
			
			private Object tryToNewInstance(Object fromItem) {
				if (fromItem == null) {
					return null;
				}
				Object existed = identityMap.get(fromItem);
				Object toItem;
				if (existed != null) {
					toItem = existed;		// the object has been copied, use the existed copy
				} else {
					if (fromItem instanceof HibernateProxy) {
						Object element = HibernateUtil.unproxy(fromItem);
						toItem = newInstance(element.getClass());
					} else {
						toItem = newInstance(fromItem.getClass());		// create a new copy
					}
					identityMap.put(fromItem, toItem);
				}
				return toItem;
			}
		};
		
		Callback callback = new Callback(idStrategy);
		ModelScanner.getInstance().scan(from, to, callback);
		return to;
	}

	public static Object convertMapToModel(Map<String, Object> item) {
		String className = (String) item.get(IDynamicModel.ENTITY_NAME);
		if (className == null) {
			return item;
		}
		
		BeanIntrospector introspector = BeanIntrospector.getInstance();
		ModelInfo modelInfo = introspector.getModelInfo(className);
		Class clazz = modelInfo.getTypeClass();
		Object model = newInstance(clazz);
		for (Entry<String, Object> entry : item.entrySet()) {
			String name = entry.getKey();
			Object value = entry.getValue();
			PropertyInfo propertyInfo = modelInfo.getProperty(name);
			propertyInfo.setValue(name, value, modelInfo);
		}
		return model;
	}
}